package evl

import (
	"testing"

	"gitcode.com/deyiyangyang/bampoo/ast"
	"gitcode.com/deyiyangyang/bampoo/obj"
)

func TestEvalStringLiteral(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	strLit := &ast.StringLiteral{Value: "hello world"}
	result, err := evaluator.Eval(strLit)

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	strVal, ok := result.(*obj.StringValue)
	if !ok {
		t.Fatalf("expected StringValue, got %T", result)
	}

	if strVal.Value != "hello world" {
		t.Errorf("expected 'hello world', got '%s'", strVal.Value)
	}
}

func TestEvalSymbol(t *testing.T) {
	env := obj.NewEnvironment()
	env.Set("test-var", &obj.StringValue{Value: "test-value"})
	evaluator := New(env)

	symbol := &ast.Symbol{Value: "test-var"}
	result, err := evaluator.Eval(symbol)

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	strVal, ok := result.(*obj.StringValue)
	if !ok {
		t.Fatalf("expected StringValue, got %T", result)
	}

	if strVal.Value != "test-value" {
		t.Errorf("expected 'test-value', got '%s'", strVal.Value)
	}
}

func TestEvalUndefinedSymbol(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	symbol := &ast.Symbol{Value: "undefined-var"}
	_, err := evaluator.Eval(symbol)

	if err == nil {
		t.Fatal("expected error for undefined symbol")
	}

	if !contains(err.Error(), "undefined symbol: undefined-var") {
		t.Errorf("expected error message to contain 'undefined symbol: undefined-var', got '%s'", err.Error())
	}
}

func TestEvalDefine(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	define := &ast.DefineExpr{
		Name:  ast.Symbol{Value: "my-var"},
		Value: &ast.StringLiteral{Value: "my-value"},
	}

	result, err := evaluator.Eval(define)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Should return nil value
	if _, ok := result.(*obj.NilValue); !ok {
		t.Fatalf("expected NilValue, got %T", result)
	}

	// Check that variable was defined
	val, ok := env.Get("my-var")
	if !ok {
		t.Fatal("expected to find my-var in environment")
	}

	strVal, ok := val.(*obj.StringValue)
	if !ok {
		t.Fatalf("expected StringValue, got %T", val)
	}

	if strVal.Value != "my-value" {
		t.Errorf("expected 'my-value', got '%s'", strVal.Value)
	}
}

func TestEvalDefineFunction(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	define := &ast.DefineExpr{
		Name: ast.Symbol{Value: "add"},
		Value: &ast.LambdaExpr{
			Parameters: []ast.Symbol{{Value: "x"}, {Value: "y"}},
			Body: []ast.Expression{
				&ast.ArithmeticExpr{
					Operator: "+",
					Operands: []ast.Expression{
						&ast.Symbol{Value: "x"},
						&ast.Symbol{Value: "y"},
					},
				},
			},
		},
	}

	result, err := evaluator.Eval(define)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Should return nil value
	if _, ok := result.(*obj.NilValue); !ok {
		t.Fatalf("expected NilValue, got %T", result)
	}

	// Check that function was defined
	val, ok := env.Get("add")
	if !ok {
		t.Fatal("expected to find add in environment")
	}

	if _, ok := val.(*obj.FunctionValue); !ok {
		t.Fatalf("expected FunctionValue, got %T", val)
	}
}

func TestEvalLambda(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// (lambda (x) x) 42
	lambda := &ast.LambdaExpr{
		Parameters: []ast.Symbol{{Value: "x"}},
		Body:       []ast.Expression{&ast.Symbol{Value: "x"}},
	}

	// Apply lambda to 42
	apply := &ast.List{
		Elements: []ast.Expression{
			lambda,
			&ast.IntegerLiteral{Value: 42},
		},
	}

	result, err := evaluator.Eval(apply)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 42 {
		t.Errorf("expected 42, got %d", intVal.Value)
	}
}

func TestEvalFunctionCall(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Define a function: (define (square x) (* x x))
	define := &ast.DefineExpr{
		Name: ast.Symbol{Value: "square"},
		Value: &ast.LambdaExpr{
			Parameters: []ast.Symbol{{Value: "x"}},
			Body: []ast.Expression{
				&ast.ArithmeticExpr{
					Operator: "*",
					Operands: []ast.Expression{
						&ast.Symbol{Value: "x"},
						&ast.Symbol{Value: "x"},
					},
				},
			},
		},
	}

	_, err := evaluator.Eval(define)
	if err != nil {
		t.Fatalf("unexpected error defining function: %v", err)
	}

	// Call (square 5)
	call := &ast.List{
		Elements: []ast.Expression{
			&ast.Symbol{Value: "square"},
			&ast.IntegerLiteral{Value: 5},
		},
	}

	result, err := evaluator.Eval(call)
	if err != nil {
		t.Fatalf("unexpected error calling function: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 25 {
		t.Errorf("expected 25, got %d", intVal.Value)
	}
}

func TestEvalProgram(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	program := &ast.Program{
		Expressions: []ast.Expression{
			&ast.DefineExpr{
				Name:  ast.Symbol{Value: "x"},
				Value: &ast.StringLiteral{Value: "program-test"},
			},
			&ast.Symbol{Value: "x"},
		},
	}

	result, err := evaluator.Eval(program)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	strVal, ok := result.(*obj.StringValue)
	if !ok {
		t.Fatalf("expected StringValue, got %T", result)
	}

	if strVal.Value != "program-test" {
		t.Errorf("expected 'program-test', got '%s'", strVal.Value)
	}
}

func TestEvalEmptyList(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	emptyList := &ast.List{Elements: []ast.Expression{}}
	result, err := evaluator.Eval(emptyList)

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if _, ok := result.(*obj.NilValue); !ok {
		t.Fatalf("expected NilValue, got %T", result)
	}
}

func TestEvalIntegerLiteral(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	tests := []struct {
		input    int64
		expected int64
	}{
		{42, 42},
		{0, 0},
		{-123, -123},
	}

	for _, tt := range tests {
		intLit := &ast.IntegerLiteral{Value: tt.input}
		result, err := evaluator.Eval(intLit)

		if err != nil {
			t.Fatalf("unexpected error for input %d: %v", tt.input, err)
		}

		intVal, ok := result.(*obj.IntegerValue)
		if !ok {
			t.Fatalf("expected IntegerValue for input %d, got %T", tt.input, result)
		}

		if intVal.Value != tt.expected {
			t.Errorf("expected value %d for input %d, got %d", tt.expected, tt.input, intVal.Value)
		}
	}
}

func TestEvalArithmeticExpression(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	tests := []struct {
		name     string
		expr     *ast.ArithmeticExpr
		expected int64
	}{
		{
			name: "simple addition",
			expr: &ast.ArithmeticExpr{
				Operator: "+",
				Operands: []ast.Expression{
					&ast.IntegerLiteral{Value: 1},
					&ast.IntegerLiteral{Value: 2},
					&ast.IntegerLiteral{Value: 3},
				},
			},
			expected: 6,
		},
		{
			name: "simple subtraction",
			expr: &ast.ArithmeticExpr{
				Operator: "-",
				Operands: []ast.Expression{
					&ast.IntegerLiteral{Value: 10},
					&ast.IntegerLiteral{Value: 3},
				},
			},
			expected: 7,
		},
		{
			name: "simple multiplication",
			expr: &ast.ArithmeticExpr{
				Operator: "*",
				Operands: []ast.Expression{
					&ast.IntegerLiteral{Value: 4},
					&ast.IntegerLiteral{Value: 5},
				},
			},
			expected: 20,
		},
		{
			name: "simple division",
			expr: &ast.ArithmeticExpr{
				Operator: "/",
				Operands: []ast.Expression{
					&ast.IntegerLiteral{Value: 20},
					&ast.IntegerLiteral{Value: 4},
				},
			},
			expected: 5,
		},
		{
			name: "negative numbers",
			expr: &ast.ArithmeticExpr{
				Operator: "+",
				Operands: []ast.Expression{
					&ast.IntegerLiteral{Value: -1},
					&ast.IntegerLiteral{Value: 2},
					&ast.IntegerLiteral{Value: -3},
				},
			},
			expected: -2,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := evaluator.Eval(tt.expr)

			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			intVal, ok := result.(*obj.IntegerValue)
			if !ok {
				t.Fatalf("expected IntegerValue, got %T", result)
			}

			if intVal.Value != tt.expected {
				t.Errorf("expected value %d, got %d", tt.expected, intVal.Value)
			}
		})
	}
}

func TestEvalArithmeticWithVariables(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Define a variable
	env.Set("x", &obj.IntegerValue{Value: 10})

	// Create arithmetic expression with variable
	expr := &ast.ArithmeticExpr{
		Operator: "+",
		Operands: []ast.Expression{
			&ast.Symbol{Value: "x"},
			&ast.IntegerLiteral{Value: 5},
		},
	}

	result, err := evaluator.Eval(expr)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 15 {
		t.Errorf("expected value 15, got %d", intVal.Value)
	}
}

func TestEvalDivisionByZero(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	expr := &ast.ArithmeticExpr{
		Operator: "/",
		Operands: []ast.Expression{
			&ast.IntegerLiteral{Value: 10},
			&ast.IntegerLiteral{Value: 0},
		},
	}

	_, err := evaluator.Eval(expr)
	if err == nil {
		t.Fatal("expected error for division by zero")
	}

	if !contains(err.Error(), "division by zero") {
		t.Errorf("expected error message to contain 'division by zero', got '%s'", err.Error())
	}
}

func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr))
}

func containsHelper(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
